using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using WCamera;

namespace GMDViewer
{
	/// <summary>
	/// This is the main type for your game
	/// </summary>
	public class GMDViewer : Microsoft.Xna.Framework.Game
	{
		GraphicsDeviceManager graphics;
		SpriteBatch spriteBatch;
		SpriteFont font;
		LineBatch lineBatch;
		GMDShader gmdShader;

		Camera camera = new Camera();
		public Camera Camera { get { return camera; } }

		static GMDViewer _SingletonInstance = null;
		public static GMDViewer Singleton { get { return _SingletonInstance; } }

		List<GMDModel> modelList = new List<GMDModel>();

		bool m_bShowNormal = true;



		public GMDViewer()
		{
			if (_SingletonInstance != null)
			{
				throw new Exception("Already had an instance.");
			}
			graphics = new GraphicsDeviceManager(this);
			_SingletonInstance = this;
			Content.RootDirectory = "Content";
		}

		/// <summary>
		/// Allows the game to perform any initialization it needs to before starting to run.
		/// This is where it can query for any required services and load any non-graphic
		/// related content.  Calling base.Initialize will enumerate through any components
		/// and initialize them as well.
		/// </summary>
		protected override void Initialize()
		{
			// TODO: Add your initialization logic here

			base.Initialize();

			camera.ViewPort = GraphicsDevice.Viewport;
		}

		/// <summary>
		/// LoadContent will be called once per game and is the place to load
		/// all of your content.
		/// </summary>
		protected override void LoadContent()
		{
			// Create a new SpriteBatch, which can be used to draw textures.
			spriteBatch = new SpriteBatch(GraphicsDevice);
			// TODO: use this.Content to load your game content here
			lineBatch = new LineBatch(GraphicsDevice);
			gmdShader = new GMDShader(lineBatch);
			gmdShader.Initialize();
			font = Content.Load<SpriteFont>("Primary");
		}

		/// <summary>
		/// UnloadContent will be called once per game and is the place to unload
		/// all content.
		/// </summary>
		protected override void UnloadContent()
		{
			// TODO: Unload any non ContentManager content here
			ClearModels();
			if (lineBatch != null)
			{
				lineBatch.Dispose();
				lineBatch = null;
			}
			gmdShader.Dispose();
		}

		/// <summary>
		/// Allows the game to run logic such as updating the world,
		/// checking for collisions, gathering input, and playing audio.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of timing values.</param>
		protected override void Update(GameTime gameTime)
		{
			// Allows the game to exit
			if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
				Keyboard.GetState().IsKeyDown(Keys.Escape))
				this.Exit();

			HandleInput();

			// TODO: Add your update logic here

			base.Update(gameTime);
		}

		/// <summary>
		/// This is called when the game should draw itself.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of timing values.</param>
		protected override void Draw(GameTime gameTime)
		{
			GraphicsDevice.Clear(Color.CornflowerBlue);

			// TODO: Add your drawing code here
			gmdShader.DrawModelLitTextured(modelList);
			gmdShader.DrawModelNormals(modelList);

			GraphicsDevice.RenderState.DepthBufferEnable = false;

			Vector3[] axisTag = new Vector3[3] {
				new Vector3(10, 0, 0), new Vector3(0, 10, 0), new Vector3(0, 0, 10)
			};

			lineBatch.AddLine(Vector3.Zero, new Vector3(10, 0, 0), Color.Red);
			lineBatch.AddLine(Vector3.Zero, new Vector3(0, 10, 0), Color.Green);
			lineBatch.AddLine(Vector3.Zero, new Vector3(0, 0, 10), Color.Blue);
			lineBatch.Render();

			Vector2[] axisTagTrans;
			camera.Project(axisTag, out axisTagTrans);
			
			spriteBatch.Begin();
			spriteBatch.DrawString(font, "X", axisTagTrans[0], Color.Red);
			spriteBatch.DrawString(font, "Y", axisTagTrans[1], Color.Green);
			spriteBatch.DrawString(font, "Z", axisTagTrans[2], Color.Blue);

			float y = font.MeasureString("Agf").Y;
			spriteBatch.DrawString(font, "Camera Position: " + camera.Position.ToString(), new Vector2(10, 10), Color.LightYellow);

			spriteBatch.End();

			GraphicsDevice.RenderState.DepthBufferEnable = true;

			base.Draw(gameTime);
		}


		private void HandleInput()
		{
			lastKeyboardState = currentKeyboardState;
			currentKeyboardState = Keyboard.GetState();
			lastMouseState = currentMouseState;
			currentMouseState = Mouse.GetState();		
	
			if(currentMouseState.LeftButton == ButtonState.Pressed)
			{
				int deltaX = currentMouseState.X - lastMouseState.X;
				int deltaY = currentMouseState.Y - lastMouseState.Y;
				float angleX = deltaX * 0.01f;
				float angleY = deltaY * 0.01f;
				camera.RotateAxisAngleCenter(camera.Up, angleX, Vector3.Zero);
				camera.RotateAxisAngleCenter(camera.Right, angleY, Vector3.Zero);
			}

			// Check for exit.
			if (currentKeyboardState.IsKeyDown(Keys.Escape))
			{
				Exit();
			}

			if (currentKeyboardState.IsKeyDown(Keys.A))
			{
				camera.SetPosition(camera.Position - camera.Right * 0.5f);
			}

			if (currentKeyboardState.IsKeyDown(Keys.W))
			{
				camera.SetPosition(camera.Position + camera.Direction * 0.5f);
			}

			if (currentKeyboardState.IsKeyDown(Keys.S))
			{
				camera.SetPosition(camera.Position - camera.Direction * 0.5f);
			}

			if (currentKeyboardState.IsKeyDown(Keys.D))
			{
				camera.SetPosition(camera.Position + camera.Right * 0.5f);
			}

			if (currentKeyboardState.IsKeyDown(Keys.Q))
			{
				camera.RotateAxisAngleCenter(camera.Up, 0.01f, Vector3.Zero);
			}

			if (currentKeyboardState.IsKeyDown(Keys.E))
			{
				camera.RotateAxisAngleCenter(camera.Up, -0.01f, Vector3.Zero);
			}

			if (currentKeyboardState.IsKeyDown(Keys.Z))
			{
				camera.SetPosition(camera.Position + camera.Up * 0.5f);
			}

			if (currentKeyboardState.IsKeyDown(Keys.X))
			{
				camera.SetPosition(camera.Position - camera.Up * 0.5f);
			}

			if (currentKeyboardState.IsKeyDown(Keys.Left))
			{
				camera.RotateAxisAngle(new Vector3(0, 1, 0), -0.01f);
			}

			if (currentKeyboardState.IsKeyDown(Keys.Right))
			{
				camera.RotateAxisAngle(new Vector3(0, 1, 0), 0.01f);
			}

			if(currentKeyboardState.IsKeyDown(Keys.C) &&
				!lastKeyboardState.IsKeyDown(Keys.C))
			{
				camera.Reset();
			}

			if(currentKeyboardState.IsKeyDown(Keys.N) && 
				!lastKeyboardState.IsKeyDown(Keys.N))
			{
				m_bShowNormal = !m_bShowNormal;
			}

			if (currentKeyboardState.IsKeyDown(Keys.L) &&
				lastKeyboardState.IsKeyUp(Keys.L))
			{
				System.Windows.Forms.OpenFileDialog ofdlg = new System.Windows.Forms.OpenFileDialog();
				ofdlg.DefaultExt = "gmd";
				ofdlg.Filter = "GMD File|*.gmd";
				if (ofdlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
				{
					ClearModels();
					GMDModel gmdModel = new GMDModel(ofdlg.FileName, GraphicsDevice);
					modelList.Add(gmdModel);
					camera.Reset();
					camera.FocusOnBoundingBox(gmdModel.m_BoundingBox);
				}
			}
		}

		protected void ClearModels()
		{
			foreach (GMDModel model in modelList)
			{
				model.Dispose();
			}
			modelList.Clear();
		}

		private KeyboardState currentKeyboardState = new KeyboardState();
		private KeyboardState lastKeyboardState = new KeyboardState();
		private MouseState currentMouseState = new MouseState();
		private MouseState lastMouseState = new MouseState();
	}
}